﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace Master
{
    /**
     * The abstract class TrafficMarshal specifies the base-functionality used in all Traffic Marshals
     * 
     * @Author A.J. Janssen
     * */
    public abstract class TrafficMarshal
    {
        protected Dictionary<Car, DirectionData> registeredCars = new Dictionary<Car, DirectionData>();

        /**
         * Method to register a specific car with the Traffic Marshal
         * */
        public void registerCar(DirectionData directionData, Car car)
        {
            if (!registeredCars.ContainsKey(car))
                registeredCars.Add(car, directionData);
        }

        /**
         * Method to remove a car from the traffic marshal
         * */
        public virtual void removeCar(Car car)
        {
            registeredCars.Remove(car);
        }

        /**
         * Method to test if the given car is allowed to move forward
         * */
        public abstract Boolean canMoveForward(Car car);

        /**
         * Method to get all registered cars coming from the right side of the given car
         * */
        protected List<Car> getCarsComingFromRelativeRightSide(List<Car> cars, Car thisCar)
        {
            return getCarsWithOrientation(cars, Toolkit.alterOrientationBy(registeredCars[thisCar].getOrientation(), -90));
        }

        /**
         * Method to get all registered cars coming from the opposite side of the given car
         * */
        protected List<Car> getCarsComingFromOppositeSide(List<Car> cars, Car thisCar)
        {
            return getCarsWithOrientation(cars, Toolkit.alterOrientationBy(registeredCars[thisCar].getOrientation(), 180));
        }

        /**
         * Method to get all registered cars coming from the left side of the given car
         * */
        protected List<Car> getCarsComingFromRelativeLeftSide(List<Car> cars, Car thisCar)
        {
            return getCarsWithOrientation(cars, Toolkit.alterOrientationBy(registeredCars[thisCar].getOrientation(), 90));
        }

        /**
         * Method to retrieve all cars with the given orientation from the specified list
         * */
        protected List<Car> getCarsWithOrientation(List<Car> cars, int orientation)
        {
            List<Car> resultCars = new List<Car>();
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getOrientation() == orientation)
                    resultCars.Add(car);
            }
            return resultCars;
        }

        /**
         * Method to retrieve all cars with the given direction from the specified list
         * */
        protected List<Car> getCarsWithDirection(List<Car> cars, Direction direction)
        {
            List<Car> resultCars = new List<Car>();
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getDirection() == direction)
                    resultCars.Add(car);
            }
            return resultCars;
        }

        /**
         * Method to retrieve all cars with the given directiondata from the specified list
         * */
        protected List<Car> getCarsWithDirectionData(List<Car> cars, DirectionData directionData)
        {
            List<Car> resultCars = new List<Car>();
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getDirection() == directionData.getDirection() &&
                    carDirectionData.getOrientation() == directionData.getOrientation())
                    resultCars.Add(car);
            }
            return resultCars;
        }

        /**
         * Method to retrieve all cars with the given statu from the specified list
         * */
        protected List<Car> getCarsWithStatus(List<Car> cars, CarStatus status)
        {
            List<Car> resultCars = new List<Car>();
            foreach (Car car in cars) if (car.getStatus() == status)
                resultCars.Add(car);
            return resultCars;
        }

        /**
         * Method to retrieve all cars with the same direction as specified in the given directiondata from the specified list
         * */
        protected Boolean onlyCarsWithSameDirection(List<Car> cars, DirectionData directionData)
        {
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getDirection() != directionData.getDirection())
                    return false;
            }
            return true;
        }

        /**
         * Method to retrieve all cars with the same orientation as specified in the given directiondata from the specified list
         * */
        protected Boolean onlyCarsWithSameOrientation(List<Car> cars, DirectionData directionData)
        {
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getOrientation() != directionData.getOrientation())
                    return false;
            }
            return true;
        }

        /**
         * Method to retrieve all cars with the same directiondata from the specified list
         * */
        protected Boolean onlyCarsWithSameDirectionData(List<Car> cars, DirectionData directionData)
        {
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getDirection() != directionData.getDirection() ||
                    carDirectionData.getOrientation() != directionData.getOrientation())
                    return false;
            }
            return true;
        }

        /**
         * Method to retrieve all cars going left from the specified list
         * */
        protected Boolean onlyCarsGoingLeft(List<Car> cars)
        {
            foreach (Car car in cars)
            {
                DirectionData carDirectionData = registeredCars[car];
                if (carDirectionData.getDirection() != Direction.LEFT)
                    return false;
            }
            return true;
        }
    }

    /**
     * BasicTrafficMarshal is the simplest implementation of TrafficMarshal.
     * This TrafficMarhsal was intended to guide cars over a simple intersection with 1 entry lane and 1 exit lane on each side (cars from opposite directions cannot turn left simultaniously)
     * 
     * @Author A.J. Janssen
     * */
    public class BasicTrafficMarshal : TrafficMarshal
    {
        /*************
         * VARIABLES *
         *************/
        protected List<Car> movingCars = new List<Car>();

        /********************
         * PUBLIC FUNCTIONS *
         ********************/

        /**
         * Method to remove a car from the traffic marshal
         * */
        public override void removeCar(Car car)
        {
            base.removeCar(car);
            movingCars.Remove(car);
        }

        /**
         * Method to test if the specified car can move forward
         * */
        public override Boolean canMoveForward(Car car)
        {
            if (!registeredCars.ContainsKey(car)) throw new InvalidOperationException(); //unregistered car
            if (movingCars.Contains(car)) return true;
            if (checkIfCarCanMove(car))
            {
                movingCars.Add(car);
                return true;
            }
            return false;
        }

        /*********************
         * PRIVATE FUNCTIONS *
         *********************/
        private Boolean checkIfCarCanMove(Car car) //check if the car can move forward
        {
            Direction carDirection = registeredCars[car].getDirection();
            if (carDirection == Direction.RIGHT) return checkIfCarCanMoveRight(car);
            if (carDirection == Direction.FORWARD) return checkIfCarCanMoveForward(car);
            return checkIfCarCanMoveLeft(car);
        }

        /**
         * Method to test if the given car can turn right
         * Car can move right if:
         * - There are no moving cars from the opposite side going left
         * - There are no moving cars from the left side going left or forward
         * */
        protected virtual Boolean checkIfCarCanMoveRight(Car car)
        {
            if (getCarsWithDirection(getCarsComingFromOppositeSide(movingCars, car), Direction.LEFT).Count == 0) //check for moving cars coming from opposite side taking a left turn
            {
                List<Car> movingCarsFromLeftSide = getCarsComingFromRelativeLeftSide(movingCars, car); //get all moving cars coming from left side
                Toolkit.removeAllFromList(movingCarsFromLeftSide, getCarsWithDirection(movingCarsFromLeftSide, Direction.RIGHT)); //remove cars going right
                return (movingCarsFromLeftSide.Count == 0);
            }
            return false;
        }

        /**
         * Method to test if the given car can move forward
         * Car can move forward if:
         * - There are no registered cars that are NOT being stopped by a traffic light cars coming from right side
         * - There are no moving cars coming from the opposite side going left
         * - There are no moving cars from the left side going left or forward
         * */
        protected virtual Boolean checkIfCarCanMoveForward(Car car)
        {
            List<Car> carsFromRightSide = getCarsComingFromRelativeRightSide(registeredCars.Keys.ToList(), car);
            Toolkit.removeAllFromList(carsFromRightSide, getCarsWithStatus(carsFromRightSide, CarStatus.WAITINGFORTRAFFICLIGHT));
            if (carsFromRightSide.Count == 0 && getCarsWithDirection(getCarsComingFromOppositeSide(movingCars, car), Direction.LEFT).Count == 0)
            {
                List<Car> carsFromLeftSide = getCarsComingFromRelativeLeftSide(movingCars, car);
                Toolkit.removeAllFromList(carsFromLeftSide, getCarsWithDirection(carsFromLeftSide, Direction.RIGHT));
                return (carsFromLeftSide.Count == 0);
            }
            return false;
        }

        /**
         * Method to test if the given car can turn left
         * Car can move left if:
         * - There are no registered cars that are NOT being stopped by a traffic light cars coming from right side
         * - There are no moving cars coming from the opposite side
         * - There are no registered cars NOT waiting for a traffic light coming from the opposite side going forward or right
         * - There are no moving cars coming from the left side going forward or left
         * */
        protected virtual Boolean checkIfCarCanMoveLeft(Car car)
        {
            List<Car> carsFromRightSide = getCarsComingFromRelativeRightSide(registeredCars.Keys.ToList(), car);
            Toolkit.removeAllFromList(carsFromRightSide, getCarsWithStatus(carsFromRightSide, CarStatus.WAITINGFORTRAFFICLIGHT));
            if (carsFromRightSide.Count == 0 && getCarsComingFromOppositeSide(movingCars, car).Count == 0)
            {
                List<Car> carsFromOppositeSide = getCarsComingFromOppositeSide(registeredCars.Keys.ToList(), car);
                Toolkit.removeAllFromList(carsFromOppositeSide, getCarsWithStatus(carsFromOppositeSide, CarStatus.WAITINGFORTRAFFICLIGHT));
                Toolkit.removeAllFromList(carsFromOppositeSide, getCarsWithDirection(carsFromOppositeSide, Direction.LEFT));
                List<Car> carsFromLeftSide = getCarsComingFromRelativeLeftSide(movingCars, car);
                Toolkit.removeAllFromList(carsFromLeftSide, getCarsWithDirection(carsFromLeftSide, Direction.RIGHT));
                return (carsFromOppositeSide.Count == 0 && carsFromLeftSide.Count == 0);
            }
            return false;
        }
    }

    /**
     * Threelanetrafficmarshal was intended to guide traffic over a intersection with 3 lanes (1 right, 1 forward and 1 left).
     * Cars on left lanes from opposite directions are allowed to move over the intersection simultaniously
     * 
     * @Author A.J. Janssen
     * */
    public class ThreeLaneTrafficMarshal : BasicTrafficMarshal
    {
        /**
         * Method to test if the given car can turn right
         * Car can move right if:
         * - There are no moving cars from the opposite side going left
         * - There are no moving cars from the left side going forward
         * */
        protected override Boolean checkIfCarCanMoveRight(Car car)
        {
            return (getCarsWithDirection(getCarsComingFromOppositeSide(movingCars, car), Direction.LEFT).Count == 0 && //check for moving cars coming from opposite side taking a left turn
                getCarsWithDirection(getCarsComingFromRelativeLeftSide(movingCars, car), Direction.FORWARD).Count == 0); //check for moving cars from the left side going forward
        }

        /**
         * Method to test if the given car can turn left
         * Car can move left if:
         * - There are no registered cars that are NOT being stopped by a traffic light cars coming from right side
         * - There are no moving cars coming from the opposite side going forward or right
         * - There are no registered cars NOT waiting for a traffic light coming from the opposite side going forward or right
         * - There are no moving cars coming from the left side going forward or left
         * */
        protected override Boolean checkIfCarCanMoveLeft(Car car)
        {
            return (checkCarsFromRightSide(car) &&
                checkMovingCarsFromOppositeSide(car) &&
                checkRegisteredCarsFromOppositeSide(car) &&
                checkMovingCarsFromLeftSide(car));
        }

        private Boolean checkCarsFromRightSide(Car car) //check cars coming from the right side
        {
            //check for cars from right site NOT being stopped by traffic light
            List<Car> carsFromRightSide = getCarsComingFromRelativeRightSide(registeredCars.Keys.ToList(), car);
            Toolkit.removeAllFromList(carsFromRightSide, getCarsWithStatus(carsFromRightSide, CarStatus.WAITINGFORTRAFFICLIGHT));
            return (carsFromRightSide.Count == 0);
        }

        private Boolean checkMovingCarsFromOppositeSide(Car car) //check moving cars coming from the opposite side
        {
            //check for moving cars from opposite side (not going left)
            List<Car> movingCarsFromOppositeSide = getCarsComingFromOppositeSide(movingCars, car);
            Toolkit.removeAllFromList(movingCarsFromOppositeSide, getCarsWithDirection(movingCarsFromOppositeSide, Direction.LEFT));
            return (movingCarsFromOppositeSide.Count == 0);
        }

        private Boolean checkRegisteredCarsFromOppositeSide(Car car) //check registered cars coming from the opposite side
        {
            List<Car> carsFromOppositeSide = getCarsComingFromOppositeSide(registeredCars.Keys.ToList(), car);
            Toolkit.removeAllFromList(carsFromOppositeSide, getCarsWithStatus(carsFromOppositeSide, CarStatus.WAITINGFORTRAFFICLIGHT));
            Toolkit.removeAllFromList(carsFromOppositeSide, getCarsWithDirection(carsFromOppositeSide, Direction.LEFT));
            return (carsFromOppositeSide.Count == 0);
        }

        private Boolean checkMovingCarsFromLeftSide(Car car) //check moving cars coming from the left side
        {
            List<Car> carsFromLeftSide = getCarsComingFromRelativeLeftSide(movingCars, car);
            Toolkit.removeAllFromList(carsFromLeftSide, getCarsWithDirection(carsFromLeftSide, Direction.RIGHT));
            return (carsFromLeftSide.Count == 0);
        }
    }
}